/*
 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { global } from "./static/global"
import { throwError } from "../utils"
import { KBoolean, KInt, KNativePointer as KPtr, nullptr } from "@koalaui/interop"
import {
    Es2pandaClassDefinitionModifiers,
    Es2pandaContextState,
    Es2pandaIdentifierFlags,
    Es2pandaImportKinds,
    Es2pandaMethodDefinitionKind,
    Es2pandaModifierFlags,
    Es2pandaPrimitiveType,
    Es2pandaScriptFunctionFlags,
    Es2pandaTokenType,
    Es2pandaVariableDeclarationKind,
    Es2pandaVariableDeclaratorFlag,
} from "../generated/Es2pandaEnums"
import {
    passNode,
    passNodeArray,
    passString,
    resolveSource,
    unpackNode,
    unpackNodeArray,
    unpackNonNullableNode,
    unpackString,
    updatePeerByNode,
} from "./utilities/private"
import { proceedToState } from "./utilities/public"
import { Es2pandaAstNodeType } from "../Es2pandaEnums"
import { AstNode } from "./peers/AstNode"
import { ArktsObject } from "./peers/ArktsObject"
import { Config } from "./peers/Config"
import { Context } from "./peers/Context"
import { factory } from "@koalaui/ets-tsc"

export const arrayOfNullptr = new BigUint64Array([nullptr])

export const allFlags: KInt =
    Object.values(Es2pandaModifierFlags).filter(
        (value: any) => { return (typeof value) === 'number' }
    ).reduce(
        (prev: KInt, next: KInt): KInt => (prev | next),
        0 as KInt
    )

export function assertValidPeer(peer: KPtr, expectedKind: Es2pandaAstNodeType): void {
    if (peer === nullptr) {
        throwError(`invalid peer: NULLPTR`)
    }
    const peerType = global.generatedEs2panda._AstNodeTypeConst(global.context, peer)
    if (peerType !== expectedKind) {
        throwError(`actual type of peer: ${peerType} expected: ${expectedKind}`)
    }
}

export class UnsupportedNode extends AstNode {
    constructor(peer: KPtr) {
        super(peer)
        console.log(`WARNING: UNSUPPORTED NODE (arkts): ${Es2pandaAstNodeType[this.type]}`)
    }
}

export class EtsScript extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE)
        super(peer)
    }

    // TODO:
    // static create(
    //     statementList: Statement[]
    // ): EtsScript {
    // }

    static fromContext(): EtsScript {
        return new EtsScript(global.es2panda._ProgramAst(global.es2panda._ContextProgram(global.context)))
    }

    /**
     * @deprecated
     */
    static createFromSource(
        source: string,
        state: Es2pandaContextState = Es2pandaContextState.ES2PANDA_STATE_PARSED,
    ): EtsScript {
        if (!global.configIsInitialized()) {
            global.config = Config.createDefault().peer
        }
        global.context = Context.createFromString(source).peer
        proceedToState(state)
        return new EtsScript(global.es2panda._ProgramAst(global.es2panda._ContextProgram(global.context)))
    }

    /**
     * @deprecated
     */
    static updateByStatements(
        node: EtsScript,
        statements: readonly AstNode[],
    ): EtsScript {
        global.generatedEs2panda._BlockStatementSetStatements(global.context, node.peer, passNodeArray(statements), statements.length)
        return node
    }

    get statements(): readonly AstNode[] {
        return unpackNodeArray(global.generatedEs2panda._BlockStatementStatements(global.context, this.peer))
    }

    set statements(nodes: readonly AstNode[]) {
        global.generatedEs2panda._BlockStatementSetStatements(global.context, this.peer, passNodeArray(nodes), nodes.length)
    }
}

export class ExpressionStatement extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_EXPRESSION_STATEMENT)
        super(peer)
        this.expression = unpackNonNullableNode(global.generatedEs2panda._ExpressionStatementGetExpression(global.context, this.peer))
    }

    static create(
        expression: AstNode,
    ): ExpressionStatement {
        return new ExpressionStatement(
            global.generatedEs2panda._CreateExpressionStatement(
                global.context,
                expression.peer
            )
        )
    }

    readonly expression: AstNode
}

// TODO: the CallExpression idl Create signature doesn't include
// the trailing block at all.
// Need to clarify with the compiler people if they will provide
// create signature with a trailing block argument.
export class CallExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_CALL_EXPRESSION)
        super(peer)
        this.expression = unpackNonNullableNode(
            global.generatedEs2panda._CallExpressionCallee(global.context, this.peer))
        this.arguments = unpackNodeArray(
            global.generatedEs2panda._CallExpressionArguments(global.context, this.peer))
        this.trailingBlock = unpackNode(
            global.generatedEs2panda._CallExpressionTrailingBlockConst(global.context, this.peer))
    }

    static create(
        expression: AstNode,
        typeParamsDecl: TSTypeParameterDeclaration | undefined,
        args: readonly AstNode[] | undefined,
        trailingBlock: AstNode | undefined = undefined
    ): CallExpression {
        const peer = global.generatedEs2panda._CreateCallExpression(
            global.context,
            passNode(expression),
            passNodeArray(args),
            args?.length ?? 0,
            passNode(typeParamsDecl),
            false,
            false
        )
        const call = new CallExpression(peer)
        if (trailingBlock) {
            global.generatedEs2panda._CallExpressionSetTrailingBlock(
                global.context, peer, trailingBlock?.peer
            )
        }
        return call
    }

    readonly expression: AstNode // Expression
    readonly arguments: readonly AstNode[]
    readonly trailingBlock: AstNode|undefined // BlockStatement
}

export class AssignmentExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ASSIGNMENT_EXPRESSION)
        super(peer)
    }

    static create(
        left: AstNode,
        assignmentOperator: Es2pandaTokenType,
        right: AstNode
    ): AssignmentExpression {
        return new AssignmentExpression(
            global.generatedEs2panda._CreateAssignmentExpression(
                global.context,
                passNode(left),
                passNode(right),
                assignmentOperator
            )
        )
    }
}

export class BinaryExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_BINARY_EXPRESSION)
        super(peer)
    }

    static create(
        left: AstNode,
        operatorType: Es2pandaTokenType,
        right: AstNode
    ): BinaryExpression {
        return new BinaryExpression(
            global.generatedEs2panda._CreateBinaryExpression(
                global.context,
                passNode(left),
                passNode(right),
                operatorType
            )
        )
    }
}

export class ETSPrimitiveType extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PRIMITIVE_TYPE)
        super(peer)
        this.primitiveType = global.generatedEs2panda._ETSPrimitiveTypeGetPrimitiveTypeConst(global.context, this.peer)
    }

    static create(
        type: Es2pandaPrimitiveType,
    ): ETSPrimitiveType {
        return new ETSPrimitiveType(
            global.generatedEs2panda._CreateETSPrimitiveType(
                global.context,
                type
            )
        )
    }

    protected override dumpMessage(): string {
        return ` <type: ${Es2pandaPrimitiveType[this.type]}>`
    }

    readonly primitiveType: Es2pandaPrimitiveType
}

export class ETSTypeReference extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_TYPE_REFERENCE)
        super(peer)
    }

    static create(
        part: ETSTypeReferencePart,
    ): ETSTypeReference {
        return new ETSTypeReference(
            global.generatedEs2panda._CreateETSTypeReference(
                global.context,
                passNode(part)
            )
        )
    }

    static createFromId(
        typeName: Identifier,
    ): ETSTypeReference {
        return new ETSTypeReference(
            global.generatedEs2panda._CreateETSTypeReference(
                global.context,
                ETSTypeReferencePart.create(typeName).peer
            )
        )
    }

    // TODO:
    // readonly typeName: Identifier
    // readonly typeRefPart: TypeReferencePart
}

export class ETSTypeReferencePart extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_TYPE_REFERENCE_PART)
        super(peer)
    }

    // TODO: support type params and prev
    static create(
        typeName: Identifier,
        typeParamsDecl?: AstNode,
    ) {
        // typeName.setReference(true)
        // TODO: support type params and prev
        return new ETSTypeReferencePart(
            global.generatedEs2panda._CreateETSTypeReferencePart(
                global.context,
                passNode(typeName),
                passNode(typeParamsDecl),
                nullptr
            )
        )
    }

    // readonly typeName: Identifier
}

export class TSUnionType extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_TS_UNION_TYPE)
        super(peer)
        this.types = unpackNodeArray(global.generatedEs2panda._TSUnionTypeTypesConst(global.context, this.peer))
    }

    static create(
        node: undefined | TSUnionType,
        types: AstNode[],
    ): TSUnionType {
        return new TSUnionType(
            updatePeerByNode(
                global.generatedEs2panda._CreateTSUnionType(
                    global.context,
                    passNodeArray(types),
                    types.length
                ),
                node
            )
        )
    }

    readonly types: readonly AstNode[]
}

export class ETSUnionType extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNION_TYPE)
        super(peer)
        this.types = unpackNodeArray(global.generatedEs2panda._TSUnionTypeTypesConst(global.context, this.peer))
    }

    static create(
        types: AstNode[],
    ): ETSUnionType {
        return new ETSUnionType(
            global.es2panda._CreateETSUnionTypeIr(
                global.context,
                passNodeArray(types),
                types.length
            )
        )
    }

    readonly types: readonly AstNode[]
}

export class ETSFunctionType extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_FUNCTION_TYPE)
        super(peer)
    }

    static create(
        signature: FunctionSignature,
        scriptFunctionFlags: Es2pandaScriptFunctionFlags
    ): ETSFunctionType {
        return new ETSFunctionType(
            global.es2panda._CreateETSFunctionTypeIr(
                global.context,
                signature.peer,
                scriptFunctionFlags
            )
        )
    }
}

export class Identifier extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_IDENTIFIER)
        super(peer)
    }

    static create(
        name: string,
        typeAnnotation?: AstNode
    ): Identifier {
        if (typeAnnotation === undefined) {
            return new Identifier(
                global.es2panda._CreateIdentifier1(global.context, passString(name))
            )
        } else {
            return new Identifier(
                global.es2panda._CreateIdentifier2(global.context, passString(name), typeAnnotation.peer)
            )
        }
    }

    protected override dumpMessage(): string {
        return ` <name: ${this.name}> <identifierFlags: ${this.identifierFlags}>`
    }

    get name(): string {
        return unpackString(global.generatedEs2panda._IdentifierName(global.context, this.peer))
    }

    set name(name: string) {
        global.generatedEs2panda._IdentifierSetName(global.context, this.peer, passString(name))
    }

    get isPrivate(): boolean {
        return global.generatedEs2panda._IdentifierIsPrivateIdentConst(global.context, this.peer)
    }

    get identifierFlags(): Es2pandaIdentifierFlags {
        return global.es2panda._IdentifierIdentifierFlags(global.context, this.peer)
    }
}

export class StringLiteral extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_STRING_LITERAL)
        super(peer)
        this.str = unpackString(global.generatedEs2panda._StringLiteralStrConst(global.context, this.peer))
    }

    static create(
        str: string,
    ): StringLiteral {
        return new StringLiteral(
            global.es2panda._CreateStringLiteral(
                global.context,
                passString(str)
            )
        )
    }

    protected override dumpMessage(): string {
        return ` <str: ${this.str}>`
    }

    readonly str: string
}

export class NumberLiteral extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_NUMBER_LITERAL)
        super(peer)
        this.value = 0.0
    }

    static create(
        value: number,
    ): NumberLiteral {
        return new NumberLiteral(
            global.es2panda._CreateNumberLiteral(
                global.context,
                value
            )
        )
    }

    protected override dumpMessage(): string {
        return ` <value: ${this.value}>`
    }

    readonly value: number = 0.0
}

export class FunctionSignature extends ArktsObject {
    constructor(peer: KPtr) {
        super(peer)
        // TODO:
        // there is some weird behavior with FunctionSignature, so properties unavailable now
        // in class constructor (typeParamsDecl maybe NULLPTR in ScriptFunction, but not NULLPTR in its signature)

        // this.typeParamsDecl = unpackNode(nativeModule._FunctionSignatureTypeParams(global.context, this.peer))
        // this.parameters = unpackNodeArray(nativeModule._FunctionSignatureParamsConst(global.context, this.peer, NULLPTR))
        // this.returnTypeAnnotation = unpackNode(nativeModule._FunctionSignatureReturnType(global.context, this.peer))
    }

    static create(
        typeParamsDecl: TSTypeParameterDeclaration | undefined,
        params: readonly AstNode[],
        returnTypeAnnotation: AstNode | undefined,
    ): FunctionSignature {
        return new FunctionSignature(
            global.es2panda._CreateFunctionSignature(
                global.context,
                passNode(typeParamsDecl),
                passNodeArray(params),
                params.length,
                passNode(returnTypeAnnotation),
                false
            )
        )
    }

    // readonly typeParamsDecl?: TSTypeParameterDeclaration
    // readonly parameters: readonly AstNode[]
    // readonly returnTypeAnnotation?: AstNode
}

export class ScriptFunction extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_SCRIPT_FUNCTION)
        super(peer)
        this.parameters = unpackNodeArray(global.generatedEs2panda._ScriptFunctionParams(global.context, this.peer))
        this.typeParamsDecl = unpackNode(global.generatedEs2panda._ScriptFunctionTypeParams(global.context, this.peer))
        this.body = unpackNode(global.generatedEs2panda._ScriptFunctionBody(global.context, this.peer))
        // this.signature = unpackNode(global.generatedEs2panda._ScriptFunctionSignature(global.context, this.peer))
        // this.declare = global.generatedEs2panda._ScriptFunctionDeclareConst(global.context, this.peer)
        this.ident = unpackNode(global.generatedEs2panda._ScriptFunctionId(global.context, this.peer))

        this.scriptFunctionFlags = this._correctScriptFunctionFlags()
    }

    static create(
        body: AstNode | undefined,
        signature: FunctionSignature | undefined,
        functionFlags: Es2pandaScriptFunctionFlags,
        modifierFlags: Es2pandaModifierFlags,
        declare: boolean,
        ident: Identifier | undefined,
        parameters?: ETSParameterExpression[],
    ): ScriptFunction {
        const peer = global.generatedEs2panda._CreateScriptFunction(
            global.context,
            passNode(body),
            signature?.peer ?? FunctionSignature.create(
                undefined,
                parameters!,
                undefined
            ).peer,
            functionFlags,
            modifierFlags
        )
        if (ident !== undefined) {
            global.generatedEs2panda._ScriptFunctionSetIdent(global.context, peer, ident.peer)
        }
        return new ScriptFunction(peer)
    }

    protected override dumpMessage(): string {
        const scriptFunctionFlags = global.generatedEs2panda._ScriptFunctionFlagsConst(global.context, this.peer)
        return ` <scriptFunctionFlags: ${scriptFunctionFlags} (${scriptFunctionFlags.toString(2).split("").reverse().join("")})>`
    }

    private _correctScriptFunctionFlags(): KInt {
        let flags: KInt = global.generatedEs2panda._ScriptFunctionFlagsConst(global.context, this.peer)
        if (this._hasReturn()) {
            flags |= Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_HAS_RETURN
            global.generatedEs2panda._ScriptFunctionAddFlag(global.context, this.peer, Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_HAS_RETURN)
        }
        if (this._isAsync()) {
            flags |= Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ASYNC
            global.generatedEs2panda._ScriptFunctionAddFlag(global.context, this.peer, Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ASYNC)
        }
        return flags
    }

    private _hasReturn(): boolean {
        if (this.body === undefined) {
            return false
        }
        let hasReturn: boolean = false
        // TODO: goes through whole subtree, optimizable (unnecessary now)
        this.body.getSubtree().forEach(
            (node: AstNode) => {
                hasReturn = hasReturn || (node.type === Es2pandaAstNodeType.AST_NODE_TYPE_RETURN_STATEMENT)
            }
        )
        return hasReturn
    }

    private _isAsync(): boolean {
        return (this.modifiers & Es2pandaModifierFlags.MODIFIER_FLAGS_ASYNC) !== 0
    }

    readonly parameters: ETSParameterExpression[]
    readonly typeParamsDecl?: TSTypeParameterDeclaration
    readonly body?: BlockStatement
    // readonly signature: FunctionSignature
    readonly scriptFunctionFlags: KInt
    readonly ident?: Identifier
}

export class ArrowFunctionExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ARROW_FUNCTION_EXPRESSION)
        super(peer)
        this.scriptFunction = unpackNonNullableNode(global.generatedEs2panda._ArrowFunctionExpressionFunction(global.context, this.peer))
    }

    static create(
        func: ScriptFunction,
    ): ArrowFunctionExpression {
        return new ArrowFunctionExpression(
            global.generatedEs2panda._CreateArrowFunctionExpression(
                global.context,
                passNode(func)
            )
        )
    }

    readonly scriptFunction: ScriptFunction
}

export class FunctionDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_FUNCTION_DECLARATION)
        super(peer)
        this.scriptFunction = unpackNonNullableNode(global.generatedEs2panda._FunctionDeclarationFunction(global.context, this.peer))
        this.parameters = unpackNodeArray(global.generatedEs2panda._ScriptFunctionParams(global.context, this.scriptFunction.peer))
        this.name = unpackNode(global.generatedEs2panda._ScriptFunctionId(global.context, this.scriptFunction.peer))
        this.body = unpackNode(global.generatedEs2panda._ScriptFunctionBody(global.context, this.scriptFunction.peer))
        this.typeParamsDecl = unpackNode(global.generatedEs2panda._ScriptFunctionTypeParams(global.context, this.scriptFunction.peer))
        this.returnType = unpackNode(global.generatedEs2panda._ScriptFunctionReturnTypeAnnotation(global.context, this.scriptFunction.peer))
        this.isAnon = global.generatedEs2panda._FunctionDeclarationIsAnonymousConst(global.context, this.peer)
    }

    static create(
        scriptFunction: ScriptFunction,
        isAnon: boolean
    ): FunctionDeclaration {
        const res = new FunctionDeclaration(
            global.es2panda._CreateFunctionDeclaration(
                global.context,
                scriptFunction.peer,
                // TODO: support annotations
                arrayOfNullptr,
                0,
                isAnon
            )
        )
        // TODO: maybe wrong
        res.modifiers = scriptFunction.modifiers
        return res
    }

    readonly scriptFunction: ScriptFunction
    readonly parameters: readonly AstNode[]
    readonly name?: Identifier
    readonly body?: BlockStatement
    readonly typeParamsDecl?: TSTypeParameterDeclaration
    readonly returnType?: AstNode
    readonly isAnon: boolean
}

export class FunctionExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_FUNCTION_EXPRESSION)
        super(peer)
        this.scriptFunction = unpackNonNullableNode(global.generatedEs2panda._FunctionExpressionFunction(global.context, this.peer))
    }

    static create(
        expression: ScriptFunction,
    ): FunctionExpression {
        return new FunctionExpression(
            global.generatedEs2panda._CreateFunctionExpression(
                global.context,
                passNode(expression)
            )
        )
    }

    readonly scriptFunction: ScriptFunction
}

export class BlockStatement extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_BLOCK_STATEMENT)
        super(peer)
        this.statements = unpackNodeArray(global.generatedEs2panda._BlockStatementStatements(global.context, this.peer))
    }

    static create(
        statements: readonly AstNode[],
    ): BlockStatement {
        return new BlockStatement(
            global.generatedEs2panda._CreateBlockStatement(
                global.context,
                passNodeArray(statements),
                statements.length
            )
        )
    }

    readonly statements: readonly AstNode[]
}

export class ETSParameterExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PARAMETER_EXPRESSION)
        super(peer)
    }

    static create(
        identifier: Identifier,
        initializer: AstNode | undefined
    ): ETSParameterExpression {
        return new ETSParameterExpression(
            global.generatedEs2panda._CreateETSParameterExpression(
                global.context,
                passNode(identifier),
                passNode(initializer)
            )
        )
    }
}

export class TSTypeParameterDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PARAMETER_DECLARATION)
        super(peer)
        this.parameters = unpackNodeArray(global.generatedEs2panda._TSTypeParameterDeclarationParamsConst(global.context, this.peer))
    }

    // TODO: think about valid type of parameters
    static create(
        parameters: AstNode[],
    ): TSTypeParameterDeclaration {
        return new TSTypeParameterDeclaration(
            global.generatedEs2panda._CreateTSTypeParameterDeclaration(
                global.context,
                passNodeArray(parameters),
                parameters.length,
                // TODO: fix
                parameters.length
            )
        )
    }

    readonly parameters: readonly TSTypeParameter[]
}

export class TSTypeParameter extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PARAMETER)
        super(peer)
        this.name = unpackNonNullableNode(global.generatedEs2panda._TSTypeParameterName(global.context, this.peer))
    }

    static create(
        name: Identifier,
        constraint: AstNode | undefined,
        defaultType: AstNode | undefined,
        modifierFlags: KInt,
    ): TSTypeParameter {
        const peer = global.generatedEs2panda._CreateTSTypeParameter(
            global.context,
            passNode(name),
            passNode(constraint),
            passNode(defaultType)
        )
        global.generatedEs2panda._AstNodeClearModifier(global.context, peer, allFlags)
        global.generatedEs2panda._AstNodeAddModifier(global.context, peer, modifierFlags)
        return new TSTypeParameter(
            peer
        )
    }

    readonly name: Identifier
}

export class ReturnStatement extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_RETURN_STATEMENT)
        super(peer)
        this.argument = unpackNode(
            global.generatedEs2panda._ReturnStatementArgument(global.context, this.peer)
        )
    }

    static create(
        argument?: AstNode,
    ): ReturnStatement {
        if (argument === undefined) {
            return new ReturnStatement(
                global.generatedEs2panda._CreateReturnStatement(
                    global.context
                )
            )
        }
        return new ReturnStatement(
            global.es2panda._CreateReturnStatement1(
                global.context,
                passNode(argument)
            )
        )
    }

    readonly argument: AstNode | undefined
}

export class IfStatement extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_IF_STATEMENT)
        super(peer)
    }

    static create(
        test: AstNode,
        consequent: AstNode,
        alternate?: AstNode
    ): IfStatement {
        return new IfStatement(
            global.generatedEs2panda._CreateIfStatement(
                global.context,
                passNode(test),
                passNode(consequent),
                passNode(alternate)
            )
        )
    }
}

export class StructDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_STRUCT_DECLARATION)
        super(peer)
        // TODO: is struct definition the same as struct definition?
        this.definition = unpackNonNullableNode(global.generatedEs2panda._ClassDeclarationDefinition(global.context, this.peer))
    }

    static create(
        definition: ClassDefinition,
    ): StructDeclaration {
        return new StructDeclaration(
            global.generatedEs2panda._CreateETSStructDeclaration(
                global.context,
                passNode(definition)
            )
        )
    }

    readonly definition: ClassDefinition
}


export class ClassDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_DECLARATION)
        super(peer)
        this.definition = unpackNonNullableNode(global.generatedEs2panda._ClassDeclarationDefinition(global.context, this.peer))
    }

    static create(
        definition: ClassDefinition,
    ): ClassDeclaration {
        return new ClassDeclaration(
            global.generatedEs2panda._CreateClassDeclaration(
                global.context,
                passNode(definition)
            )
        )
    }

    readonly definition: ClassDefinition
}

export class ClassDefinition extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_DEFINITION)
        super(peer)
        this.name = unpackNonNullableNode(global.generatedEs2panda._ClassDefinitionIdent(global.context, this.peer))
        this.members = unpackNodeArray(global.generatedEs2panda._ClassDefinitionBody(global.context, this.peer))
        this.typeParamsDecl = unpackNode(global.generatedEs2panda._ClassDefinitionTypeParamsConst(global.context, this.peer))
        this.superClass = unpackNode(global.generatedEs2panda._ClassDefinitionSuper(global.context, this.peer))
        this.modifiers = global.generatedEs2panda._ClassDefinitionModifiersConst(global.context, this.peer)
    }

    static create(
        name: Identifier | undefined,
        members: readonly AstNode[],
        modifiers: KInt,
        classFlags: KInt,
        typeParamsDecl: TSTypeParameterDeclaration | undefined,
        superClass?: AstNode,
    ): ClassDefinition {
        const peer = global.es2panda._CreateClassDefinition1(
            global.context,
            passNode(name),
            passNodeArray(members),
            members.length,
            classFlags,
            modifiers
        )
        global.generatedEs2panda._ClassDefinitionSetTypeParams(global.context, peer, passNode(typeParamsDecl))
        global.generatedEs2panda._ClassDefinitionSetSuper(global.context, peer, passNode(superClass))
        return new ClassDefinition(
            peer
        )
    }

    readonly name: Identifier
    readonly members: readonly AstNode[]
    readonly typeParamsDecl?: TSTypeParameterDeclaration
    readonly superClass?: AstNode
}

export class ClassStaticBlock extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_STATIC_BLOCK)
        super(peer)
    }

    // TODO:
    // static create(
    //     value: AstNode
    // ): ClassStaticBlock {
    //     return new ClassStaticBlock(
    //         nativeModule._CreateClassStaticBlock(
    //             global.context,
    //             passNode(value)
    //         )
    //     )
    // }
}

export class MethodDefinition extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_METHOD_DEFINITION)
        super(peer)
        this.scriptFunction = unpackNonNullableNode(global.generatedEs2panda._MethodDefinitionFunction(global.context, this.peer))
        this.name = unpackNonNullableNode(global.generatedEs2panda._ScriptFunctionId(global.context, this.scriptFunction.peer))
        this.kind = global.generatedEs2panda._MethodDefinitionKindConst(global.context, this.peer)
    }

    static create(
        kind: Es2pandaMethodDefinitionKind,
        key: AstNode,
        value: AstNode,
        modifiers: KInt,
        isComputed: boolean
    ): MethodDefinition {
        return new MethodDefinition(
            global.generatedEs2panda._CreateMethodDefinition(
                global.context,
                kind,
                passNode(key),
                passNode(value),
                modifiers,
                isComputed
            )
        )
    }

    readonly scriptFunction: ScriptFunction
    readonly name: Identifier
    readonly kind: Es2pandaMethodDefinitionKind
}

export class ClassElement extends AstNode {
    constructor(peer: KPtr) {
        super(peer)
        this.key = unpackNonNullableNode(global.generatedEs2panda._ClassElementKey(global.context, this.peer))
        this.value = unpackNode(global.generatedEs2panda._ClassElementValue(global.context, this.peer))
    }

    readonly key: AstNode
    readonly value?: AstNode
}

export class ClassProperty extends ClassElement {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_PROPERTY)
        super(peer)
    }

    static create(
        key: AstNode,
        value: AstNode,
        typeAnnotation: AstNode,
        modifiers: KInt,
        isComputed: boolean
    ): ClassProperty {
        return new ClassProperty(
            global.generatedEs2panda._CreateClassProperty(
                global.context,
                passNode(key),
                passNode(value),
                passNode(typeAnnotation),
                modifiers,
                isComputed
            )
        )
    }
}

export class VariableDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_VARIABLE_DECLARATION)
        super(peer)
        this.declarationKind = global.generatedEs2panda._VariableDeclarationKindConst(global.context, this.peer)
        this.declarators = unpackNodeArray(global.generatedEs2panda._VariableDeclarationDeclaratorsConst(global.context, this.peer))
    }

    static create(
        modifiers: KInt,
        kind: Es2pandaVariableDeclarationKind,
        declarators: readonly VariableDeclarator[]
    ): VariableDeclaration {
        const peer = global.generatedEs2panda._CreateVariableDeclaration(
            global.context,
            kind,
            passNodeArray(declarators),
            declarators.length
        )
        global.generatedEs2panda._AstNodeClearModifier(global.context, peer, allFlags)
        global.generatedEs2panda._AstNodeAddModifier(global.context, peer, modifiers)
        return new VariableDeclaration(peer)
    }

    readonly declarationKind: Es2pandaVariableDeclarationKind
    readonly declarators: readonly VariableDeclarator[]
}

export class VariableDeclarator extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_VARIABLE_DECLARATOR)
        super(peer)
        this.name = unpackNonNullableNode(global.generatedEs2panda._VariableDeclaratorId(global.context, this.peer))
    }

    static create(
        flag: Es2pandaVariableDeclaratorFlag,
        name: Identifier,
        initializer: AstNode | undefined
    ): VariableDeclarator {
        const peer = global.generatedEs2panda._CreateVariableDeclarator(
            global.context,
            flag,
            passNode(name)
        )
        if (initializer !== undefined) {
            global.generatedEs2panda._VariableDeclaratorSetInit(global.context, peer, initializer.peer)
        }
        return new VariableDeclarator(peer)
    }

    readonly name: Identifier
}

export class ETSUndefinedType extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNDEFINED_TYPE)
        super(peer)
    }
}

export class SuperExpression extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_SUPER_EXPRESSION)
        super(peer)
    }

    static create(
    ): SuperExpression {
        return new SuperExpression(
            global.generatedEs2panda._CreateSuperExpression(
                global.context,
            )
        )
    }
}

export class ImportSource extends ArktsObject {
    constructor(peer: KPtr) {
        super(peer)
    }

    static create(
        source: StringLiteral,
        hasDecl: boolean,
    ): ImportSource {
        return new ImportSource(
            global.es2panda._CreateImportSource(
                global.context,
                source.peer,
                resolveSource(source).peer,
                hasDecl
            )
        )
    }
}

export class EtsImportDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ETS_IMPORT_DECLARATION)
        super(peer)
        this.importSource = unpackNonNullableNode(global.generatedEs2panda._ETSImportDeclarationSourceConst(global.context, this.peer))
        this.importSpecifiers = unpackNodeArray(global.generatedEs2panda._ImportDeclarationSpecifiersConst(global.context, this.peer))
        this.resolvedSource = unpackNonNullableNode(global.generatedEs2panda._ETSImportDeclarationResolvedSource(global.context, this.peer))
        this.importKind = global.generatedEs2panda._ImportDeclarationIsTypeKindConst(global.context, this.peer)
        this.hasDecl = global.generatedEs2panda._ETSImportDeclarationHasDeclConst(global.context, this.peer)
    }

    static create(
        source: StringLiteral,
        specifiers: readonly ImportSpecifier[],
        importKind: Es2pandaImportKinds,
        hasDecl: boolean,
    ): EtsImportDeclaration {
        return new EtsImportDeclaration(
            global.generatedEs2panda._CreateETSImportDeclaration(
                global.context,
                ImportSource.create(
                    source,
                    hasDecl,
                ).peer,
                passNodeArray(specifiers),
                specifiers.length,
                +importKind
            )
        )
    }

    readonly importSource: StringLiteral
    readonly resolvedSource: StringLiteral
    readonly importSpecifiers: readonly ImportSpecifier[]
    readonly importKind: Es2pandaImportKinds
    readonly hasDecl: boolean
}

export class ImportSpecifier extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_IMPORT_SPECIFIER)
        super(peer)
    }

    static create(
        imported: Identifier,
        local: Identifier,
    ): ImportSpecifier {
        return new ImportSpecifier(
            global.generatedEs2panda._CreateImportSpecifier(
                global.context,
                passNode(imported),
                passNode(local),
            )
        )
    }
}

export class AnnotationUsageIr extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_ANNOTATION_USAGE)
        super(peer)
        this.expr = unpackNonNullableNode(global.generatedEs2panda._AnnotationUsageIrExpr(global.context, this.peer))
        this.properties = unpackNodeArray(global.generatedEs2panda._AnnotationUsageIrPropertiesConst(global.context, this.peer))
    }

    // TODO:
    // static create

    readonly expr: AstNode
    readonly properties: readonly ClassProperty[]
}

export class TSTypeParameterInstantiation extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_PARAMETER_INSTANTIATION)
        super(peer)
    }

    static create(
        parameters: readonly AstNode[],
    ): TSTypeParameterInstantiation {
        return new TSTypeParameterInstantiation(
            global.generatedEs2panda._CreateTSTypeParameterInstantiation(
                global.context,
                passNodeArray(parameters),
                parameters.length
            )
        )
    }
}

export class TSInterfaceDeclaration extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_TS_INTERFACE_DECLARATION)
        super(peer)
    }

    static create(
        extendsParams: readonly AstNode[],
        id: AstNode,
        typeParams: AstNode,
        body: AstNode,
        isStatic: KBoolean,
        isExternal: KBoolean,
    ): TSInterfaceDeclaration {
        return new TSInterfaceDeclaration(
            global.generatedEs2panda._CreateTSInterfaceDeclaration(
                global.context,
                passNodeArray(extendsParams),
                extendsParams.length,
                passNode(id),
                passNode(typeParams),
                passNode(body),
                isStatic,
                isExternal,
            )
        )
    }
}

export class UndefinedLiteral extends AstNode {
    constructor(peer: KPtr) {
        assertValidPeer(peer, Es2pandaAstNodeType.AST_NODE_TYPE_UNDEFINED_LITERAL)
        super(peer)
    }
}
